using System;
using System.Linq;

namespace UnitTesting.Fixturing
{
	public static class DoExtensions
	{
		public static void Do(this DefaultFixture fixture, Action action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1>(this DefaultFixture fixture, Action<T1> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2>(
			this DefaultFixture fixture, Action<T1, T2> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3>(
			this DefaultFixture fixture, Action<T1, T2, T3> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5, T6>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5, T6> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5, T6, T7>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5, T6, T7> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5, T6, T7, T8>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> action)
		{
			DoVoidImp(fixture, action);
		}

		public static void Do<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
			this DefaultFixture fixture, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> action)
		{
			DoVoidImp(fixture, action);
		}

		public static TReturn Do<TReturn>(
			this DefaultFixture fixture, Func<TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, TReturn>(
			this DefaultFixture fixture, Func<T1, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, T6, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, T6, T7, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, T6, T7, T8, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, T6, T7, T8, T9, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn Do<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TReturn> func)
		{
			return (TReturn) DoReturnImp(fixture, func);
		}

		public static TReturn FreezeDo<TReturn>(
			this DefaultFixture fixture, Func<TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, TReturn>(
			this DefaultFixture fixture, Func<T1, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, T6, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, T6, T7, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, T6, T7, T8, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, T6, T7, T8, T9, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		public static TReturn FreezeDo<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TReturn>(
			this DefaultFixture fixture, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TReturn> func)
		{
			Ensure.IsNotNull(() => fixture);
			var result = (TReturn) DoReturnImp(fixture, func);
			fixture.FreezeInstance(result);
			return result;
		}

		static void DoVoidImp(DefaultFixture fixture, Delegate @delegate)
		{
			Ensure.IsNotNull(() => fixture);
			Ensure.IsNotNull(() => @delegate);
			var args = GetArgumentsForVoid(fixture, @delegate);
			@delegate.Method.Invoke(@delegate.Target, args);
		}

		static object[] GetArgumentsForVoid(DefaultFixture fixture, Delegate @delegate)
		{
			return @delegate.GetType().GetGenericArguments()
				.Select(x => CreateInstance(fixture, x))
				.ToArray();
		}

		static object CreateInstance(DefaultFixture fixture, Type type)
		{
			return fixture.ValueProvider.Create(type);
		}

		static object DoReturnImp(DefaultFixture fixture, Delegate @delegate)
		{
			Ensure.IsNotNull(() => fixture);
			Ensure.IsNotNull(() => @delegate);
			var args = GetArgumentsForReturn(fixture, @delegate);
			return @delegate.Method.Invoke(@delegate.Target, args);
		}

		static object[] GetArgumentsForReturn(DefaultFixture fixture, Delegate @delegate)
		{
			var types = @delegate.GetType().GetGenericArguments();
			return Enumerable.Range(0, types.Length - 1)
				.Select(x => CreateInstance(fixture, types[x]))
				.ToArray();
		}
	}
}